#include "../includes/FileApi.h"
#include <bits/types/FILE.h>
#include <cstddef>
#include <sys/types.h>
#include <vector>
#include <algorithm>

FileApi* FileApi::instance_ = nullptr;

FileApi& FileApi::getInstance(){
    if(instance_ == nullptr){
        instance_ = new FileApi;
    }
    return *instance_;
}

// 默认构造函数
FileApi::FileApi(){
    std::string disk_file_path = "/home/ubuntu/FileSysSimulate/file-sys-simulate/disk_file"; // 用于模拟磁盘的文件路径
    DiskManager::getInstance(disk_file_path); // 先初始化1个磁盘管理器
}

// 打开文件, 获得文件句柄
int FileApi::open(const std::string& path){
    DiskManager& disk_manager = DiskManager::getInstance();
    DiskManager::Inode inode;

    // 查询该文件是否存在
    if(disk_manager.inode_get(path, inode) == -1) {
        // 新建一个inode节点(新建一个文件)
        if(disk_manager.new_inode(path) == -1){
            return -1;
        }
        disk_manager.inode_get(path, inode);
    }

    // 1个进程最多打开1024个文件
    for(int i = 0; i < 1024; i++){
        if(!file_table.count(i)){ // 选择1个未被使用过的文件句柄
            // 直接在容器中构造对象，避免拷贝
            file_table.emplace(i, FileTableItem(inode.id, 0));
            return i; // i此时为文件句柄
        }
    } 
    return -1; // 文件打开表已经满了
}

// 从缓冲区写入数据到文件, 偏移量由进程自动维护
int FileApi::write(int fd, void* buff, size_t size){
    if(!file_table.count(fd)){ // 该句柄是一个无效的句柄
        return -1;
    }
    DiskManager& disk_manager = DiskManager::getInstance();
    FileTableItem& item = file_table[fd]; // 取出文件打开表项的引用

    item.mtx.lock(); // 加锁, 在此函数执行期间, 不允许其他线程读写该文件

    
    int inode_id = item.inode_id; // 取出该句柄对应的inode节点id
    DiskManager::Inode inode;
    if(disk_manager.inode_get(inode_id, inode) == -1){ // 寻找inode节点失败
        return -1;
    }
    
    // 一次性分配好所需磁盘空间
    std::vector<int> added_block; // 存储新增的数据块id, 用于数据块分配失败时的回溯
    while(inode.size < item.offset + size){ // 当inode持有的空间大小少于需要写入的内容大小
        int new_block_id = disk_manager.new_data_block(); // 新分配一个数据块
        if(new_block_id == -1){ // 分配失败, 应该回滚之前分配的数据块
            for(auto block_id : added_block){
                disk_manager.data_block_del(block_id); // 释放已经分配的数据块
            }
            item.mtx.unlock(); // 解锁, 允许其他线程访问该文件
            return -1;
        }
        added_block.push_back(new_block_id);
        inode.blocks[inode.block_num] = new_block_id;
        inode.block_num ++;
        if(inode.size + BLOCK_SIZE < item.offset + size)inode.size += BLOCK_SIZE;
        else inode.size = item.offset + size; // 将文件大小记录为偏移量的最大字节数
    }

    disk_manager.inode_set(inode_id, inode); // 将对inode的修改写入磁盘
    
    // 将任意类型的指针转化成字节指针, 可通过数组索引来对数据进行分块
    const char* src_bytes = static_cast<const char*>(buff);
    
    // 执行实际的磁盘写入
    off_t offset = item.offset; // 当前的读写偏移量
    size_t writed_size = 0; // 已经写入的字节数
    while(writed_size < size){
        int block_index = offset / BLOCK_SIZE;
        int block_id = inode.blocks[block_index]; // 应该从第几个数据块开始写

        off_t inner_off = offset - (block_index * BLOCK_SIZE); // 计算块内偏移量
        size_t left_size = size - writed_size; // 剩余未写的字节数
        size_t to_write_size = std::min(left_size, (size_t)BLOCK_SIZE); // 本次可写入的数据量

        if(disk_manager.data_block_write(block_id, (void*)(src_bytes + writed_size), to_write_size, inner_off) == -1){
            item.mtx.unlock(); // 解锁, 允许其他线程访问该文件
            return -1;
        }
        writed_size += to_write_size; // 更新已经写的数据大小
        offset += to_write_size; // 偏移量右移
    }
    item.offset = offset; // 修改文件偏移量
    item.mtx.unlock(); // 解锁, 允许其他线程访问该文件
    return size;
}

// 从文件中读取数据到缓冲区, 偏移量由进程自动维护
int FileApi::read(int fd, void *buff, size_t size){
    if(!file_table.count(fd)){ // 该句柄是一个无效的句柄
        return -1;
    }
    DiskManager& disk_manager = DiskManager::getInstance();

    // 获取文件打开表项并加锁
    FileTableItem& item = file_table[fd];
    item.mtx.lock();

    // 获取文件句柄所对应的inode
    int inode_id = item.inode_id;
    DiskManager::Inode inode;
    if(disk_manager.inode_get(inode_id, inode) == -1){
        item.mtx.unlock();
        return -1;
    }
    
    // 将任意类型的指针转化成字节指针, 可通过数组索引来对数据进行分块
    const char* src_bytes = static_cast<const char*>(buff);

    // 执行实际的磁盘读取, 每次读取不超过BLOCK_SIZE大小的数据块
    off_t offset = item.offset; // 当前的文件读写偏移量
    size_t readed_size = 0; // 已经读取的字节数
    size = std::min(size, inode.size - offset); // 本次可读取的最大字节数应该不多于文件大小-当前偏移量
    while(readed_size < size){
        int block_index = offset / BLOCK_SIZE; 
        int block_id = inode.blocks[block_index];
        off_t inner_off = offset - (block_index * BLOCK_SIZE); // 第block_id个数据块的内部偏移量
        size_t left_size = size - readed_size; // 剩余未读的字节数
        size_t to_read_size = std::min(left_size, (size_t)BLOCK_SIZE); // 本次可读取的数据量

        if(disk_manager.data_block_read(block_id, (void *)(src_bytes + readed_size), to_read_size, inner_off) == -1){
            item.mtx.unlock();
            return -1;
        }
        readed_size += to_read_size; // 更新已经写入的字节数
        offset += to_read_size; // 更新当前读写的偏移量
        if(to_read_size == left_size)break; // 当前已经读完了整个文件
    }
    item.offset = offset; // 更新进程维护的文件读写偏移量
    item.mtx.unlock();
    return readed_size;
}

int FileApi::lseek(int fd, off_t offset, int whence){
    if(!file_table.count(fd)){ // 该句柄是一个无效的句柄
        return -1;
    }
    FileTableItem& item = file_table[fd];
    item.mtx.lock();
    int inode_id = item.inode_id;
    DiskManager::Inode inode;
    DiskManager& disk_manager = DiskManager::getInstance();
    if(disk_manager.inode_get(inode_id, inode) == -1){
        item.mtx.unlock();
        return -1;
    }
    if(whence == FILE_START){ // 将文件描述表项中的文件读写偏移量置为offset
        item.offset = offset;
        item.mtx.unlock();
        return item.offset;
    }
    else if(whence == FILE_CUR){ // 将文件描述表项中的文件读写偏移量增加offset
        item.offset += offset;
        item.offset = std::min(item.offset, (off_t)inode.size); // 偏移量不能超过文件大小
        item.mtx.unlock();
        return item.offset;
    }
    else if(whence == FILE_END){ // 将文件描述表项中的文件读写偏移量置为文件末尾
        item.offset = inode.size;
        item.mtx.unlock();
        return item.offset;
    }
    else {
        item.mtx.unlock();
        return -1;
    }
}

int FileApi::close(int fd){
    // 关闭一个文件
    if(!file_table.count(fd)){ // 无效的文件句柄
        return -1;
    }
    file_table.erase(fd); // 从进程管理的文件描述符表中删除该句柄
    return 1;
}

int FileApi::del(int fd){
    // 删除1个文件
    if(!file_table.count(fd)){ // 无效的文件句柄
        return -1;
    }
    FileTableItem& item = file_table[fd];
    int inode_id = item.inode_id;
    DiskManager& disk_manager = DiskManager::getInstance();
    file_table.erase(fd); // 从文件打开表中删除
    return disk_manager.inode_del(inode_id); // 删除其对应的inode节点
}